package cc.shacocloud.mirage.utils;

import cc.shacocloud.mirage.loader.Launcher;
import cc.shacocloud.mirage.utils.charSequence.StrUtil;
import cc.shacocloud.mirage.utils.map.ConcurrentReferenceHashMap;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.net.*;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.Map;
import java.util.Objects;
import java.util.jar.JarFile;
import java.util.jar.Manifest;


/**
 * 应用工具库
 *
 * @author 思追(shaco)
 */
@Slf4j
public class AppUtil {
    
    private static final long JVM_NAME_RESOLVE_THRESHOLD = 250;
    private static final long HOST_NAME_RESOLVE_THRESHOLD = 250;
    
    private static final Map<Class<?>, File> CLASS_SOURCE_FILE_CACHE = new ConcurrentReferenceHashMap<>();
    
    private static String pid;
    
    private static final Object pidLock = new Object();
    
    private static Class<?> startClass;
    
    private static final Object startClassLock = new Object();
    
    /**
     * 判断当前是否是在 jar 中运行的
     */
    public static boolean isJarRun() {
        Class<?> startClass = getStartClass();
        return startClass != null;
    }
    
    /**
     * 获取应用 pid
     */
    public static String getPid() {
        if (Objects.isNull(pid)) {
            synchronized (pidLock) {
                if (Objects.isNull(pid)) {
                    String jvmName = resolveJvmName();
                    pid = jvmName.split("@")[0];
                }
            }
        }
        return pid;
    }
    
    /**
     * 获取 hostName
     */
    public static String getHostName() throws UnknownHostException {
        long startTime = System.currentTimeMillis();
        
        String hostName = InetAddress.getLocalHost().getHostName();
        
        long resolveTime = System.currentTimeMillis() - startTime;
        if (resolveTime > HOST_NAME_RESOLVE_THRESHOLD) {
            if (log.isWarnEnabled()) {
                StringBuilder warning = new StringBuilder();
                warning.append("InetAddress.getLocalHost().getHostName() 耗时");
                warning.append(resolveTime);
                warning.append(" 毫秒后才响应，请验证您的网络配置！");
                
                if (System.getProperty("os.name").toLowerCase().contains("mac")) {
                    warning.append(" (macOS 机器可能需要向 /etc/hosts 添加条目)");
                }
                
                warning.append(".");
                log.warn(warning.toString());
            }
        }
        
        return hostName;
    }
    
    /**
     * 获取应用启动类，如果未获取到返回 null
     */
    @Nullable
    public static Class<?> getStartClass() {
        if (Objects.isNull(startClass)) {
            synchronized (startClassLock) {
                if (Objects.isNull(startClass)) {
                    try {
                        ClassLoader classLoader = AppUtil.class.getClassLoader();
                        Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
                        while (resources.hasMoreElements()) {
                            try (InputStream inputStream = resources.nextElement().openStream()) {
                                Manifest manifest = new Manifest(inputStream);
                                String startClassStr = manifest.getMainAttributes().getValue(Launcher.START_CLASS_ATTRIBUTE);
                                if (startClassStr != null) {
                                    startClass = ClassUtil.forName(startClassStr, AppUtil.class.getClassLoader());
                                    break;
                                }
                            }
                        }
                    } catch (Exception ignore) {
                    }
                }
            }
        }
        
        return startClass;
    }
    
    /**
     * 获取应用启动文件目录
     */
    @NotNull
    public static File getStartDir(@Nullable Class<?> sourceClass) {
        File homeDir = findSource((sourceClass != null) ? sourceClass : getStartClass());
        homeDir = (homeDir != null) ? homeDir : getHomeDir();
        if (homeDir.isFile()) {
            homeDir = homeDir.getParentFile();
        }
        homeDir = homeDir.exists() ? homeDir : new File(".");
        return homeDir.getAbsoluteFile();
    }
    
    /**
     * 返回用于查找主目录的基础源。这通常是 jar 文件或目录。如果无法确定源，则返回 null
     */
    @Nullable
    public static File findSource(@Nullable Class<?> sourceClass) {
        if (Objects.isNull(sourceClass)) return null;
        
        return CLASS_SOURCE_FILE_CACHE.computeIfAbsent(sourceClass, k -> {
            try {
                ProtectionDomain domain = sourceClass.getProtectionDomain();
                CodeSource codeSource = (domain != null) ? domain.getCodeSource() : null;
                URL location = (codeSource != null) ? codeSource.getLocation() : null;
                File source = (location != null) ? findSource(location) : null;
                if (source != null && source.exists() && !isUnitTest()) {
                    return source.getAbsoluteFile();
                }
            } catch (Exception ignore) {
            }
            return null;
        });
    }
    
    /**
     * 是否是单元测试，如果是返回 true 反之 false
     */
    public static boolean isUnitTest() {
        try {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            for (int i = stackTrace.length - 1; i >= 0; i--) {
                if (stackTrace[i].getClassName().startsWith("org.junit.")) {
                    return true;
                }
            }
        } catch (Exception ignore) {
        }
        return false;
    }
    
    /**
     * 获取用户默认目录
     */
    @NotNull
    public static File getHomeDir() {
        String userDir = System.getProperty("user.dir");
        return new File(StrUtil.isNotEmpty(userDir) ? userDir : ".");
    }
    
    /**
     * 获取 jdk 版本号
     */
    @NotNull
    public static String getJdkVersion() {
        return System.getProperty("java.version");
    }
    
    /**
     * 获取启动的系统用户
     */
    @NotNull
    public static String getUserName() {
        return System.getProperty("user.name");
    }
    
    @NotNull
    private static File findSource(@NotNull URL location) throws IOException, URISyntaxException {
        URLConnection connection = location.openConnection();
        if (connection instanceof JarURLConnection) {
            return getRootJarFile(((JarURLConnection) connection).getJarFile());
        }
        return new File(location.toURI());
    }
    
    @Contract("_ -> new")
    private static @NotNull File getRootJarFile(@NotNull JarFile jarFile) {
        String name = jarFile.getName();
        int separator = name.indexOf("!/");
        if (separator > 0) {
            name = name.substring(0, separator);
        }
        return new File(name);
    }
    
    /**
     * 解析 jvm 名称
     */
    private static String resolveJvmName() {
        long startTime = System.currentTimeMillis();
        String jvmName = ManagementFactory.getRuntimeMXBean().getName();
        long elapsed = System.currentTimeMillis() - startTime;
        
        if (elapsed > JVM_NAME_RESOLVE_THRESHOLD) {
            if (log.isWarnEnabled()) {
                StringBuilder warning = new StringBuilder();
                warning.append("ManagementFactory.getRuntimeMXBean().getName() 耗时 ");
                warning.append(elapsed);
                warning.append(" 毫秒后才响应，这可能是由于主机名解析速度较慢，请验证您的网络配置！");
                
                if (System.getProperty("os.name").toLowerCase().contains("mac")) {
                    warning.append(" (macOS 机器可能需要向 /etc/hosts 添加条目)");
                }
                
                warning.append(".");
                log.warn(warning.toString());
            }
            
        }
        return jvmName;
    }
}
